﻿// Class1.cpp
#include "pch.h"
#include "Model3D.h"
#include <ppltasks.h>
#include <ppl.h>
#include "Helpers.h"
#include <collection.h>

using namespace DXBase;
using namespace DXBase::Loader;

using namespace Assimp;

using namespace Platform;
using namespace concurrency;

PostProcessStep Model3D::GetDefaultPPSteps()
{
	// default pp steps
	static PostProcessStep ppsteps = 
		PostProcessStep::CalcTangentSpace | // calculate tangents and bitangents if possible
		PostProcessStep::JoinIdenticalVertices    | // join identical vertices/ optimize indexing
		PostProcessStep::ValidateDataStructure    | // perform a full validation of the loader's output
		PostProcessStep::ImproveCacheLocality     | // improve the cache locality of the output vertices
		PostProcessStep::RemoveRedundantMaterials | // remove redundant materials
		PostProcessStep::FindDegenerates          | // remove degenerated polygons from the import
		PostProcessStep::FindInvalidData          | // detect invalid model data, such as invalid normal vectors
		PostProcessStep::GenUVCoords              | // convert spherical, cylindrical, box and planar mapping to proper UVs
		PostProcessStep::TransformUVCoords        | // preprocess UV transformations (scaling, translation ...)
		PostProcessStep::FindInstances            | // search for instanced meshes and remove them by references to one master
		PostProcessStep::LimitBoneWeights         | // limit bone weights to 4 per vertex
		PostProcessStep::OptimizeMeshes		   | // join small meshes, if possible;
		PostProcessStep::SplitByBoneCount         | // split meshes with too many bones. Necessary for our (limited) hardware skinning shader

		// extra nice steps
		PostProcessStep::GenSmoothNormals		   | // generate smooth normal vectors if not existing
		PostProcessStep::SplitLargeMeshes         | // split large, unrenderable meshes into submeshes
		PostProcessStep::Triangulate			   | // triangulate polygons with more than 3 edges
		PostProcessStep::ConvertToLeftHanded	   | // convert everything to D3D left handed space
		PostProcessStep::SortByPType              | // make 'clean' meshes which consist of a single typ of primitives
		(PostProcessStep)0
		;
	return ppsteps;
}
PostProcessStep Model3D::defaultPPSteps = GetDefaultPPSteps();

Model3D::Model3D()
{
	// TODO set importer properties!!
	//aiPropertyStore* props = aiCreatePropertyStore();
	//aiSetImportPropertyInteger(props,AI_CONFIG_IMPORT_TER_MAKE_UVS,1);
	//aiSetImportPropertyFloat(props,AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,g_smoothAngle);
	//aiSetImportPropertyInteger(props,AI_CONFIG_PP_SBP_REMOVE,nopointslines ? aiPrimitiveType_LINE | aiPrimitiveType_POINT : 0 );
	//aiSetImportPropertyInteger(props,AI_CONFIG_GLOB_MEASURE_TIME,1);
	////aiSetImportPropertyInteger(props,AI_CONFIG_PP_PTV_KEEP_HIERARCHY,1);
	//
	//const PropertyMap* pp = reinterpret_cast<const PropertyMap*>(props);
	//ImporterPimpl* pimpl = imp->Pimpl();
	//pimpl->mIntProperties = pp->ints;
	//pimpl->mFloatProperties = pp->floats;
	//pimpl->mStringProperties = pp->strings;
}


Windows::Foundation::IAsyncOperation<Model3D^>^ Model3D::LoadFile(String^ file, PostProcessStep ppSteps)
{
	// get the full path
	file = IOHelper::FullPath(file);

	auto ldTask = create_task([file, ppSteps] () -> Model3D^
	{
		if (!file)
			return nullptr;

		std::vector<char> cpath = ToCString(file);
		auto result = ref new Model3D();
		result->scene = result->importer.ReadFile(&cpath[0], (unsigned int)ppSteps);
		if (!result->scene)
		{
			LastLoadError = ToWString(result->importer.GetErrorString());
			return nullptr;
		}
		result->filename = file;
		result->PrepareModel();
		LastLoadError = nullptr;
		return result;
	});
	return create_async([ldTask] { return ldTask; });
}

Windows::Foundation::IAsyncOperation<Model3D^>^ Model3D::LoadFile(Windows::Storage::StorageFile^ file, PostProcessStep ppSteps)
{
	std::vector<char> pTip;
	auto fname = file->Path;
	for (auto p = fname->End(); p != fname->Begin(); p--)
		if (p[0] == '.')
		{
			// what about ogre's .mesh.xml?
			while (p != fname->End())
				pTip.push_back((char)*p++);
			break;
		}
	pTip.push_back(0);

	auto readt = create_task(Windows::Storage::FileIO::ReadBufferAsync(file))
		.then([file, pTip, ppSteps] (Windows::Storage::Streams::IBuffer^ buffer) -> Model3D^ 
		{
			auto buf = GetData(buffer);

			auto result = ref new Model3D();
			result->scene = result->importer.ReadFileFromMemory(buf, buffer->Length, (unsigned int)ppSteps, &pTip[0]);
			if (!result->scene)
			{
				LastLoadError = ToWString(result->importer.GetErrorString());
				return nullptr;
			}
			result->filename = file->Path;
			result->PrepareModel();
			LastLoadError = nullptr;
			return result;
		});
	return create_async([readt] { return readt; });
}
Windows::Foundation::IAsyncOperation<Model3D^>^ Model3D::PickFile(PostProcessStep ppSteps)
{
	Windows::Storage::Pickers::FileOpenPicker picker;
	{
		// construct extensions list
		std::string extensions;
		Assimp::Importer importer;
		importer.GetExtensionList(extensions);
		int start = 1;
		std::vector<wchar_t> xext;
		for (size_t i = 0; i < extensions.length(); i++)
		{
			if (extensions[i] == ';' || i+1 == extensions.length())
			{
				if (i+1 == extensions.length())
					i++;
				xext.resize(i-start);
				size_t j;
				for (j = start; j < i; j++)
					xext[j-start] = extensions[j]; // assume they are plain ASCII that can be translated as is
				start = i+2;

				// doesn't support double dot (as is '.mesh.xml')
				for (j = 1; j < xext.size(); j++)
					if (xext[j] == '.')
						break;
				if (j != xext.size()) continue;

				auto anExt = ref new Platform::String(&xext[0], xext.size());
				picker.FileTypeFilter->Append(anExt);
			}
		}
	}
	// other settings
	picker.ViewMode = Windows::Storage::Pickers::PickerViewMode::List;
	picker.SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::DocumentsLibrary;

	auto topen = create_task(picker.PickSingleFileAsync())
		.then([ppSteps](Windows::Storage::StorageFile^ file)
		{
			if (file)
				return create_task(LoadFile(file, ppSteps));
			return create_task([] () -> Model3D^ { return nullptr; });
		});
	return create_async([topen] () { return topen; });
}


void Model3D::PrepareModel()
{
	CreateSceneData();
}
int Model3D::CreateSceneData()
{
#if DONE
	if (!g_pcAsset)return 0;

	// reset all subsystems
	CMaterialManager::Instance().Reset();
	CDisplay::Instance().Reset();

	for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i)
	{
		const aiMesh* mesh = g_pcAsset->pcScene->mMeshes[i];

		// create the material for the mesh
		if (!g_pcAsset->apcMeshes[i]->piEffect)	{
			CMaterialManager::Instance().CreateMaterial(
				g_pcAsset->apcMeshes[i],mesh);
		}

		// create vertex buffer
		if(FAILED( g_piDevice->CreateVertexBuffer(sizeof(AssetHelper::Vertex) *
			mesh->mNumVertices,
			D3DUSAGE_WRITEONLY,
			0,
			D3DPOOL_DEFAULT, &g_pcAsset->apcMeshes[i]->piVB,NULL)))	{
			MessageBox(g_hDlg,"Failed to create vertex buffer",
				"ASSIMP Viewer Utility",MB_OK);
			return 2;
		}

		DWORD dwUsage = 0;
		if (g_pcAsset->apcMeshes[i]->piOpacityTexture || 1.0f != g_pcAsset->apcMeshes[i]->fOpacity)
			dwUsage |= D3DUSAGE_DYNAMIC;

		unsigned int nidx;
		switch (mesh->mPrimitiveTypes) {
			case aiPrimitiveType_POINT:
				nidx = 1;break;
			case aiPrimitiveType_LINE:
				nidx = 2;break;
			case aiPrimitiveType_TRIANGLE:
				nidx = 3;break;
			default: ai_assert(false);
		};

		// check whether we can use 16 bit indices
		if (mesh->mNumFaces * 3 >= 65536)	{
			// create 32 bit index buffer
			if(FAILED( g_piDevice->CreateIndexBuffer( 4 *
				mesh->mNumFaces * nidx,
				D3DUSAGE_WRITEONLY | dwUsage,
				D3DFMT_INDEX32,
				D3DPOOL_DEFAULT, 
				&g_pcAsset->apcMeshes[i]->piIB,
				NULL)))
			{
				MessageBox(g_hDlg,"Failed to create 32 Bit index buffer",
					"ASSIMP Viewer Utility",MB_OK);
				return 2;
			}

			// now fill the index buffer
			unsigned int* pbData;
			g_pcAsset->apcMeshes[i]->piIB->Lock(0,0,(void**)&pbData,0);
			for (unsigned int x = 0; x < mesh->mNumFaces;++x)
			{
				for (unsigned int a = 0; a < nidx;++a)
				{
					*pbData++ = mesh->mFaces[x].mIndices[a];
				}
			}
		}
		else	{
			// create 16 bit index buffer
			if(FAILED( g_piDevice->CreateIndexBuffer( 2 *
				mesh->mNumFaces * nidx,
				D3DUSAGE_WRITEONLY | dwUsage,
				D3DFMT_INDEX16,
				D3DPOOL_DEFAULT,
				&g_pcAsset->apcMeshes[i]->piIB,
				NULL)))
			{
				MessageBox(g_hDlg,"Failed to create 16 Bit index buffer",
					"ASSIMP Viewer Utility",MB_OK);
				return 2;
			}

			// now fill the index buffer
			uint16_t* pbData;
			g_pcAsset->apcMeshes[i]->piIB->Lock(0,0,(void**)&pbData,0);
			for (unsigned int x = 0; x < mesh->mNumFaces;++x)
			{
				for (unsigned int a = 0; a < nidx;++a)
				{
					*pbData++ = (uint16_t)mesh->mFaces[x].mIndices[a];
				}
			}
		}
		g_pcAsset->apcMeshes[i]->piIB->Unlock();

		// collect weights on all vertices. Quick and careless
		std::vector<std::vector<aiVertexWeight> > weightsPerVertex( mesh->mNumVertices);
		for( unsigned int a = 0; a < mesh->mNumBones; a++)	{
			const aiBone* bone = mesh->mBones[a];
			for( unsigned int b = 0; b < bone->mNumWeights; b++)
				weightsPerVertex[bone->mWeights[b].mVertexId].push_back( aiVertexWeight( a, bone->mWeights[b].mWeight));
		}

		// now fill the vertex buffer
		AssetHelper::Vertex* pbData2;
		g_pcAsset->apcMeshes[i]->piVB->Lock(0,0,(void**)&pbData2,0);
		for (unsigned int x = 0; x < mesh->mNumVertices;++x)
		{
			pbData2->vPosition = mesh->mVertices[x];

			if (NULL == mesh->mNormals)
				pbData2->vNormal = aiVector3D(0.0f,0.0f,0.0f);
			else pbData2->vNormal = mesh->mNormals[x];

			if (NULL == mesh->mTangents)	{
				pbData2->vTangent = aiVector3D(0.0f,0.0f,0.0f);
				pbData2->vBitangent = aiVector3D(0.0f,0.0f,0.0f);
			}
			else	{
				pbData2->vTangent = mesh->mTangents[x];
				pbData2->vBitangent = mesh->mBitangents[x];
			}

			if (mesh->HasVertexColors( 0))	{
				pbData2->dColorDiffuse = D3DCOLOR_ARGB(
					((unsigned char)std::max( std::min( mesh->mColors[0][x].a * 255.0f, 255.0f),0.0f)),
					((unsigned char)std::max( std::min( mesh->mColors[0][x].r * 255.0f, 255.0f),0.0f)),
					((unsigned char)std::max( std::min( mesh->mColors[0][x].g * 255.0f, 255.0f),0.0f)),
					((unsigned char)std::max( std::min( mesh->mColors[0][x].b * 255.0f, 255.0f),0.0f)));
			}
			else pbData2->dColorDiffuse = D3DCOLOR_ARGB(0xFF,0xff,0xff,0xff);

			// ignore a third texture coordinate component
			if (mesh->HasTextureCoords( 0))	{
				pbData2->vTextureUV = aiVector2D(
					mesh->mTextureCoords[0][x].x,
					mesh->mTextureCoords[0][x].y);
			}
			else pbData2->vTextureUV = aiVector2D(0.5f,0.5f);

			if (mesh->HasTextureCoords( 1))	{
				pbData2->vTextureUV2 = aiVector2D(
					mesh->mTextureCoords[1][x].x,
					mesh->mTextureCoords[1][x].y);
			}
			else pbData2->vTextureUV2 = aiVector2D(0.5f,0.5f);

			// Bone indices and weights
			if( mesh->HasBones())	{
				unsigned char boneIndices[4] = { 0, 0, 0, 0 };
				unsigned char boneWeights[4] = { 0, 0, 0, 0 };
				ai_assert( weightsPerVertex[x].size() <= 4);
				for( unsigned int a = 0; a < weightsPerVertex[x].size(); a++)
				{
					boneIndices[a] = weightsPerVertex[x][a].mVertexId;
					boneWeights[a] = (unsigned char) (weightsPerVertex[x][a].mWeight * 255.0f);
				}

				memcpy( pbData2->mBoneIndices, boneIndices, sizeof( boneIndices));
				memcpy( pbData2->mBoneWeights, boneWeights, sizeof( boneWeights));
			} else
			{
				memset( pbData2->mBoneIndices, 0, sizeof( pbData2->mBoneIndices));
				memset( pbData2->mBoneWeights, 0, sizeof( pbData2->mBoneWeights));
			}

			++pbData2;
		}
		g_pcAsset->apcMeshes[i]->piVB->Unlock();

		// now generate the second vertex buffer, holding all normals
		if (!g_pcAsset->apcMeshes[i]->piVBNormals)	{
			GenerateNormalsAsLineList(g_pcAsset->apcMeshes[i],mesh);
		}
	}
#endif
	return 1;
}